/*
 * Layer.cpp
 *
 *  Created on: 04/01/2012
 *      Author: luiz
 */

#include <sstream>
#include <iostream>
#include <algorithm>
#include <set>
#include <stack>
#include <vector>
#include <map>

#include "Layer.h"


Layer::Layer(int grid_size_x,int grid_size_y,int number_of_positions_used_per_cell)
:grid_size_x_(grid_size_x),grid_size_y_(grid_size_y),number_of_positions_used_per_cell_(number_of_positions_used_per_cell)
{
    initialize();
}

Layer::Layer(const Layer & layer)
{
    grid_size_x_ = layer.grid_size_x_;
    grid_size_y_ = layer.grid_size_y_;
    initialize();
    number_of_positions_used_per_cell_ =  layer.number_of_positions_used_per_cell_;
    for (int i = 0; i < (int)layer.cell_list_.size(); ++i) {
        Cell *cell= this->create_new_cell_at(layer.cell_list_[i]->cell_position().x(),layer.cell_list_[i]->cell_position().y());
        cell->set_cell_name(layer.cell_list_[i]->cell_name());
        cell->set_cell_function(layer.cell_list_[i]->cell_function());
        cell->set_cell_clock_zone(layer.cell_list_[i]->cell_clock_zone());
        cell->set_cell_polarization(layer.cell_list_[i]->cell_polarization());
    }

}

Layer::~Layer(){
    deinitilize();
}

void Layer::initialize()
{
    if(grid_size_x_  > 0)
    {
        layer_grid = new Cell**[grid_size_x_];
        for (int i = 0; i < grid_size_x_; ++i) {
            layer_grid[i] = new Cell*[grid_size_y_];
            for (int j = 0; j < grid_size_y_; ++j) {
                layer_grid[i][j] = NULL;
            }
        }
    } else {
        layer_grid = NULL;
    }
}

template <typename T>
void delete_cell_item(T * item){
    delete item;
}

void Layer::deinitilize()
{
    if(layer_grid!=NULL){
        // free memory
        for (int i = 0; i < grid_size_x_; ++i) {
            delete[] layer_grid[i];
        }
        delete[] layer_grid;
    }
    std::for_each(cell_list_.begin(), cell_list_.end(), delete_cell_item<Cell>);
    cell_list_.clear();
}



Layer & Layer::operator =(const Layer & layer)
{
    if(this == &layer){
        return *this;
    }

    deinitilize();
    grid_size_x_ = layer.grid_size_x_;
    grid_size_y_ = layer.grid_size_y_;
    initialize();
    number_of_positions_used_per_cell_ =  layer.number_of_positions_used_per_cell_;
    for (int i = 0; i < (int)layer.cell_list_.size(); ++i) {
        Cell *cell= this->create_new_cell_at(layer.cell_list_[i]->cell_position().x(),layer.cell_list_[i]->cell_position().y());
        cell->set_cell_name(layer.cell_list_[i]->cell_name());
        cell->set_cell_function(layer.cell_list_[i]->cell_function());
        cell->set_cell_clock_zone(layer.cell_list_[i]->cell_clock_zone());
        cell->set_cell_polarization(layer.cell_list_[i]->cell_polarization());
    }
    //cell_list_ = layer.cell_list_;
    return *this;
}


Cell *Layer::create_new_cell_at(int position_x,int position_y)
{
    // verify if this position is valid - test boundary limit
    // reduce 1 unity because the position start with 0
    if(position_x+number_of_positions_used_per_cell_-1 < grid_size_x_
            && position_y+number_of_positions_used_per_cell_-1 < grid_size_y_
            && position_x >=0 && position_y >=0)
    {

        // verify if there isn't other cell in the same place
        // only need to check edges of the cell
        // reduce 1 unity because the position start with 0
        if(layer_grid[position_x][position_y]==NULL &&
                layer_grid[position_x+number_of_positions_used_per_cell_-1][position_y]==NULL &&
                layer_grid[position_x][position_y+number_of_positions_used_per_cell_-1]==NULL &&
                layer_grid[position_x+number_of_positions_used_per_cell_-1][position_y+number_of_positions_used_per_cell_-1]==NULL)
        {



            cell_list_.push_back(new Cell(cell_list_.size()));

            //fill all positions with cell's pointer address
            for (int i = position_x; i < position_x+number_of_positions_used_per_cell_; ++i)
            {
                for (int j = position_y; j < position_y+number_of_positions_used_per_cell_; ++j)
                {
                    layer_grid[i][j] = cell_list_[cell_list_.size()-1];
                }
            }
            cell_list_[cell_list_.size()-1]->set_cell_position(position_x,position_y);

            return cell_list_[cell_list_.size()-1];
        }

    }

    // return null if some limit is exceeded
    return NULL;
}

int Layer::number_of_cells() const
{
    return cell_list_.size();
}

Cell *Layer::get_cell_at_list_index(int cell_index)
{
    return cell_list_[cell_index];
}


Cell *Layer::get_cell_by_id(int id)
{
    for (int i = 0; i < (int)cell_list_.size(); ++i) {
        if( cell_list_[i]->cell_id() == id){
            return cell_list_[i];
        }
    }
    return NULL;
}

int Layer::number_of_cells_by_function(Cell::CELL_FUNCTION function)
{
    int number =0;
    for (int i = 0; i < (int)cell_list_.size(); ++i) {
        if( cell_list_[i]->cell_function() == function){
            number++;
        }
    }
    return number;
}

bool Layer::verify_insertion(int position_x, int position_y)
{

    if(position_x+number_of_positions_used_per_cell_-1 < grid_size_x_
            && position_y+number_of_positions_used_per_cell_-1 < (grid_size_y_)
            && position_x >=0 && position_y >=0 )
    {

        // verify if there isn't other cell in the same place
        // only need to check edges of the cell
        // reduce 1 unity because the position start with 0
        if(layer_grid[position_x][position_y]!=NULL ||
                layer_grid[position_x][position_y+number_of_positions_used_per_cell_-1]!=NULL ||
                layer_grid[position_x+number_of_positions_used_per_cell_-1][position_y]!=NULL ||
                layer_grid[position_x+number_of_positions_used_per_cell_-1][position_y+number_of_positions_used_per_cell_-1]!=NULL
        )
        {

            return false;

        }

        // int neighborhood_radius = number_of_positions_used_per_cell_/2;

        int  neighborhood_radius = 1;
        for (int i = -neighborhood_radius; i < number_of_positions_used_per_cell_+neighborhood_radius; ++i) {
            for (int j = -neighborhood_radius; j < (number_of_positions_used_per_cell_/2); ++j) {
                int pos_x = position_x+i;
                int pos_y = position_y+j;
                if(pos_x < 0 || pos_y < 0 || pos_x >= grid_size_x_  || pos_y >= grid_size_y_ ) continue;
                if(layer_grid[pos_x][pos_y]!=NULL){
                    if(layer_grid[pos_x][pos_y]->cell_function()!=Cell::OUTPUT_CELL_FUNCTION){
                        return true;
                    }
                }
            }
        }
        return false;
    }
    return false;
}

int Layer::grid_size_x() const
{
    return grid_size_x_;
}

int Layer::grid_size_y() const
{
    return grid_size_y_;
}

int Layer::number_of_positions_used_per_cell()
{
    return number_of_positions_used_per_cell_;
}

void Layer::set_number_of_positions_used_per_cell(int number_of_positions_used_per_cell)
{
    number_of_positions_used_per_cell_ = number_of_positions_used_per_cell;
}

int Layer::get_number_of_cells() const
{
    return cell_list_.size();
}

std::vector<Cell*> Layer::cell_list()
{
    return cell_list_;
}
/*
 * Get all empty places on neighborhood of some cell
 */
std::vector<std::pair<int,int> > Layer::get_empty_neighborhood(Cell * cell, Layer * layer){
    /* Look at places with (+)
     *            y
     *  |  -  |   +   |  +  |
     *x |  -  |  cell |  +  |
     *  |  -  |   +   |  +  |
     */
    std::vector<std::pair<int,int> > cell_places_list;
    int cell_neighborhodd_radius = 1;
    for(int i=cell->cell_position_horizontal()-cell_neighborhodd_radius; i < cell->cell_position_horizontal()+layer->number_of_positions_used_per_cell()+cell_neighborhodd_radius; ++i ){
        for (int j = cell->cell_position_vertical(); j < cell->cell_position_vertical()+layer->number_of_positions_used_per_cell()+cell_neighborhodd_radius; ++j) {
            if(layer->verify_insertion(i,j)){
                cell_places_list.push_back(std::make_pair<int,int>(i,j));
            }
        }
    }
    return cell_places_list;
}

std::set<std::pair<int,int> > Layer::get_list_empty_places()
{
    std::set<std::pair<int,int> > empty_places;
    for (std::vector<Cell*>::iterator cell = cell_list_.begin(); cell != cell_list_.end(); ++cell) {
        std::vector<std::pair<int,int> > cell_neighborhood = get_empty_neighborhood(*cell,this);
        for (std::vector<std::pair<int,int> >::iterator place = cell_neighborhood.begin();  place != cell_neighborhood.end(); ++place) {
            empty_places.insert(*place);
        }
    }
    return empty_places;
}

void Layer::remove_cell_at(int position_x, int position_y)
{
    Cell* cell = get_cell_at_position(position_x,position_y);
    std::vector<Cell*>::iterator it = std::find(cell_list_.begin(), cell_list_.end(), cell);
    if(it!=cell_list_.end()) {
        *it = *cell_list_.rbegin();
        cell_list_.pop_back();
        //fill all positions with cell's pointer address
        for (int i = position_x; i < position_x+number_of_positions_used_per_cell_; ++i)
        {
            for (int j = position_y; j < position_y+number_of_positions_used_per_cell_; ++j)
            {
                layer_grid[i][j] = NULL;
            }
        }
    }
}

void Layer::remove_cell(Cell *cell)
{
    remove_cell_at(cell->cell_position_horizontal(), cell->cell_position_vertical());
}

bool Layer::verify_conex_before_remove(Cell *cell_remove)
{

    /*
     * IMPLEMENTAR UMA LOOKUP TABLE E USAR INDICES (VETOR COM LOBELS) AO INVES DE SET COIM CELULAS
     * A BUSCA E MAIS RAPIDA E SEM OVERHEAD
     */

    //obtem as celulas de entrada e saida
    std::vector<Cell*> input_cells = get_list_cells_by_function(this,Cell::INPUT_CELL_FUNCTION);
    std::vector<Cell*> ouput_cells = get_list_cells_by_function(this,Cell::OUTPUT_CELL_FUNCTION);


    // para cada saida, armazena se a partir delas as entradas foram alcancadas
    std::vector<int> outputs_reached;
    for (int n = 0; n < (int)ouput_cells.size(); ++n) {

        // cria um set com todas as celulas analisadas em uma busca
        // nesta busca deve contem todas as celuas de entrada
        std::set<Cell*> cells_lookup_analyzed;

        // cria um set com todas as celulas de entrada.
        // a lista deve conter todas as celulas ao fim da busca
        std::set<Cell*> cells_input_analyzed;

        //comeca a busca por uma das saidas
        std::stack<Cell*> stack_cells;
        stack_cells.push(ouput_cells[n]);

        // cells_lookup_analyzed.insert(ouput_cells[n]);

        //distancia que se considera vizinhanca
        //int neighborhood_radius = number_of_positions_used_per_cell_/2;
        int neighborhood_radius = 1;

        while(!stack_cells.empty()){

            Cell * cell = stack_cells.top();
            stack_cells.pop();

            // se a celula nao foi explorada (e uma aresta de retorno, para desconsiderar)
            // adiciono na pilha para analise
            if(cells_lookup_analyzed.find(cell) == cells_lookup_analyzed.end()){

                cells_lookup_analyzed.insert(cell);

                //verifica se é uma celula de entrada
                // adiciona todas as celulas de entrada
                if(cell->cell_function() == Cell::INPUT_CELL_FUNCTION){
                    cells_input_analyzed.insert(cell);
                }

                // analisa toda a vizinhanca desde as celulas do canto esquerda superior até o canto direito inferior
                for (int i = -neighborhood_radius; i < (number_of_positions_used_per_cell_+neighborhood_radius); ++i) {

                    for (int j = -neighborhood_radius; j < (number_of_positions_used_per_cell_+neighborhood_radius); ++j) {

                        int pos_x  = cell->cell_position().x()+i;
                        int pos_y = cell->cell_position().y()+j;

                        // se houver celula na posicao analisada
                        if(  (pos_x >= 0 ) &&  (pos_y  >= 0 )
                                && (pos_x < (grid_size_x_-neighborhood_radius) )
                                && (pos_y < (grid_size_y_-neighborhood_radius) )
                        )
                        {
                            Cell* neighborhood_cell = layer_grid[cell->cell_position().x()+i][cell->cell_position().y()+j];
                            if(neighborhood_cell!=NULL && neighborhood_cell!=cell && neighborhood_cell->cell_function() != Cell::FIXED_CELL_FUNCTION && cell_remove != neighborhood_cell){


                                //verifica se é uma celula de entrada
                                // adiciona a celula na lista de  celulas de entrada
                                if(neighborhood_cell->cell_function() == Cell::INPUT_CELL_FUNCTION){
                                    cells_input_analyzed.insert(neighborhood_cell);
                                } else
                                    //verifica se nao foi analizada a vizinhanca
                                    if(cells_lookup_analyzed.find(neighborhood_cell) == cells_lookup_analyzed.end())
                                        stack_cells.push(neighborhood_cell);
                            }
                        }


                    }
                }
            }


        }
        // se o numero de celulas de entradas alcancadas for o mesmo da quantidade de celulas de entrada existents no circuito
        // a partir de uma saida e possivel alcancar todas as entradas
        if(cells_input_analyzed.size() == input_cells.size()){
            outputs_reached.push_back(n);
        }

    }
    if(outputs_reached.size() == ouput_cells.size()){
        return true;
    }
    return false;

}

Cell *Layer::get_lower_clock_zone_cell_neighborhood(Cell *cell, Layer *layer)
{
    /* Look at places with (+)
     *            y
     *  |  -  |   +   |  +  |
     *x |  -  |  cell |  +  |
     *  |  -  |   +   |  +  |
     */
    int cell_neighborhodd_radius = 1;
    Cell* cell_return  = get_cell_at_position((cell->cell_position_horizontal()-cell_neighborhodd_radius),cell->cell_position_vertical());
    for(int i=cell->cell_position_horizontal()-cell_neighborhodd_radius; i < cell->cell_position_horizontal()+layer->number_of_positions_used_per_cell()+cell_neighborhodd_radius; ++i ){
        for (int j = cell->cell_position_vertical(); j < cell->cell_position_vertical()+layer->number_of_positions_used_per_cell()+cell_neighborhodd_radius; ++j) {
            if(i >= layer->grid_size_x() || j >= layer->grid_size_y()) continue;
            Cell * cell_pos = layer->get_cell_at_position(i,j);
            if(cell_return == NULL || ( cell_pos != NULL && cell_return->cell_clock_zone() < layer->get_cell_at_position(i,j)->cell_clock_zone()) )
            {
                cell_return = layer->get_cell_at_position(i,j);
            }
        }
    }
    return cell_return;
}

std::string Layer::print_layer()
{
    std::stringstream oss;
    oss << "Layer\n";
    for (int i = 0; i < grid_size_x_; ++i) {
        for (int j = 0; j < grid_size_y_; ++j) {
            if(layer_grid[i][j]!=NULL){
                oss << layer_grid[i][j]->print_cell();
                oss << "\t";
            } else {
                oss << 0;
                //oss << layer_grid[j][i];
                oss << "\t\t";
            }
        }
        oss << "\n";
    }
    return oss.str();
}



std::vector<Cell*> Layer::get_list_cells_by_function(Layer *layer,Cell::CELL_FUNCTION cell_function ) {
    std::vector<Cell *> list;
    for (int i = 0; i < layer->number_of_cells(); ++i) {
        if(layer->get_cell_at_list_index(i)->cell_function()==cell_function)
        {
            list.push_back(layer->get_cell_at_list_index(i));
        }
    }
    return list;

}

bool Layer::verify_conex()
{


    /*
     * IMPLEMENTAR UMA LOOKUP TABLE E USAR INDICES (VETOR COM LOBELS) AO INVES DE SET COIM CELULAS
     * A BUSCA E MAIS RAPIDA E SEM OVERHEAD
     */

    //obtem as celulas de entrada e saida
    std::vector<Cell*> input_cells = get_list_cells_by_function(this,Cell::INPUT_CELL_FUNCTION);
    std::vector<Cell*> ouput_cells = get_list_cells_by_function(this,Cell::OUTPUT_CELL_FUNCTION);


    // para cada saida, armazena se a partir delas as entradas foram alcancadas
    std::vector<int> outputs_reached;
    for (int n = 0; n < (int)ouput_cells.size(); ++n) {

        // cria um set com todas as celulas analisadas em uma busca
        // nesta busca deve contem todas as celuas de entrada
        std::set<Cell*> cells_lookup_analyzed;

        // cria um set com todas as celulas de entrada.
        // a lista deve conter todas as celulas ao fim da busca
        std::set<Cell*> cells_input_analyzed;

        //comeca a busca por uma das saidas
        std::stack<Cell*> stack_cells;
        stack_cells.push(ouput_cells[n]);

        // cells_lookup_analyzed.insert(ouput_cells[n]);

        //distancia que se considera vizinhanca
        //int neighborhood_radius = number_of_positions_used_per_cell_/2;
        int neighborhood_radius = 1;

        while(!stack_cells.empty()){

            Cell * cell = stack_cells.top();
            stack_cells.pop();

            // se a celula nao foi explorada (e uma aresta de retorno, para desconsiderar)
            // adiciono na pilha para analise
            if(cells_lookup_analyzed.find(cell) == cells_lookup_analyzed.end()){

                cells_lookup_analyzed.insert(cell);

                //verifica se é uma celula de entrada
                // adiciona todas as celulas de entrada
                if(cell->cell_function() == Cell::INPUT_CELL_FUNCTION){
                    cells_input_analyzed.insert(cell);
                }

                // analisa toda a vizinhanca desde as celulas do canto esquerda superior até o canto direito inferior
                for (int i = -neighborhood_radius; i < (number_of_positions_used_per_cell_+neighborhood_radius); ++i) {

                    for (int j = -neighborhood_radius; j < (number_of_positions_used_per_cell_+neighborhood_radius); ++j) {

                        int pos_x  = cell->cell_position().x()+i;
                        int pos_y = cell->cell_position().y()+j;

                        // se houver celula na posicao analisada
                        if(  (pos_x >= 0 ) &&  (pos_y  >= 0 )
                                && (pos_x < (grid_size_x_-neighborhood_radius) )
                                && (pos_y < (grid_size_y_-neighborhood_radius) )
                        )
                        {
                            Cell* neighborhood_cell = layer_grid[cell->cell_position().x()+i][cell->cell_position().y()+j];
                            if(neighborhood_cell!=NULL && neighborhood_cell!=cell && neighborhood_cell->cell_function() != Cell::FIXED_CELL_FUNCTION){


                                //verifica se é uma celula de entrada
                                // adiciona a celula na lista de  celulas de entrada
                                if(neighborhood_cell->cell_function() == Cell::INPUT_CELL_FUNCTION){
                                    cells_input_analyzed.insert(neighborhood_cell);
                                } else
                                    //verifica se nao foi analizada a vizinhanca
                                    if(cells_lookup_analyzed.find(neighborhood_cell) == cells_lookup_analyzed.end())
                                        stack_cells.push(neighborhood_cell);
                            }
                        }


                    }
                }
            }


        }
        // se o numero de celulas de entradas alcancadas for o mesmo da quantidade de celulas de entrada existents no circuito
        // a partir de uma saida e possivel alcancar todas as entradas
        if(cells_input_analyzed.size() == input_cells.size()){
            outputs_reached.push_back(n);
        }

    }
    if(outputs_reached.size() == ouput_cells.size()){
        return true;
    }
    return false;

}

Cell * Layer::get_cell_at_position(int pos_x, int pos_y)
{
    if(pos_x < grid_size_x_ && pos_y < grid_size_y_ &&pos_x >=0 && pos_y >=0)
        return layer_grid[pos_x][pos_y];
    return NULL;
}

int Layer::get_output_shift()
{
    //realiza uma busca em profundidade em cada entrada para se chegar na saida

    // int max_shift=0;

    /*
     * Busca em profundidade
     */

    //obtem as celulas de entrada e saida
    std::vector<Cell*> input_cells = get_list_cells_by_function(this,Cell::INPUT_CELL_FUNCTION);
    std::vector<Cell*> ouput_cells = get_list_cells_by_function(this,Cell::OUTPUT_CELL_FUNCTION);


    // para cada entrada, armazena se a partir dela as saidas foram alcancadas
    std::vector<int> outputs_reached;

    for (int n = 0; n < (int)input_cells.size(); ++n) {

        int shift = 0;

        // cria um set com todas as celulas analisadas em uma busca
        // nesta busca deve contem todas as celuas de entrada
        std::set<Cell*> cells_lookup_analyzed;

        // cria um set com todas as celulas de saidas.
        // esta lista deve conter todas as celulas ao fim da busca
        std::set<Cell*> cells_ouput_analyzed;

        //comeca a busca por uma das entradas
        std::stack<Cell*> stack_cells;
        stack_cells.push(input_cells[n]);

        // armazena, para cada celula, o shift atual da busca, deve permanecer sempre o maior
        int cell_labels_shift_clock_zone[cell_list_.size()];

        //distancia que se considera vizinhanca
        int neighborhood_radius = number_of_positions_used_per_cell_/2;

        //Cell * last_cell_analized = NULL;

        while(!stack_cells.empty()){

            Cell * cell = stack_cells.top();
            stack_cells.pop();

            // se a celula nao foi explorada (eh uma aresta de retorno, para desconsiderar)
            // adiciono na pilha para analise
            if(cells_lookup_analyzed.find(cell) == cells_lookup_analyzed.end()){

                cells_lookup_analyzed.insert(cell);

                //verifica se é uma celula de saida
                if(cell->cell_function() == Cell::OUTPUT_CELL_FUNCTION){
                    cells_ouput_analyzed.insert(cell);
                }

                // analisa toda a vizinhanca desde as celulas do canto esquerda superior até o canto direito inferior
                for (int i = -neighborhood_radius; i < (number_of_positions_used_per_cell_+neighborhood_radius); ++i) {

                    for (int j = -neighborhood_radius; j < (number_of_positions_used_per_cell_+neighborhood_radius); ++j) {

                        int pos_x  = cell->cell_position().x()+i;
                        int pos_y = cell->cell_position().y()+j;

                        if(  (pos_x >= 0 ) &&  (pos_y  >= 0 )
                                && (pos_x < (grid_size_x_-neighborhood_radius) )
                                && (pos_y < (grid_size_y_-neighborhood_radius) )
                        )
                        {

                            Cell* neighborhood_cell = layer_grid[cell->cell_position().x()+i][cell->cell_position().y()+j];

                            // se houver celula na posicao analisada
                            if(neighborhood_cell!=NULL && neighborhood_cell!=cell){


                                //obtem o indice no vetor de celulas
                                int index = 0;
                                for (; index < (int)cell_list_.size(); ++index) {
                                    if(cell == cell_list_[i]) break;
                                }
                                if(cell_labels_shift_clock_zone[index] < shift){
                                    cell_labels_shift_clock_zone[index] = shift;
                                }

                                //verifica se é uma celula de entrada
                                // adiciona a celula na lista de  celulas de entrada
                                if(neighborhood_cell->cell_function() == Cell::INPUT_CELL_FUNCTION){
                                    cells_ouput_analyzed.insert(neighborhood_cell);
                                }

                                if(cells_lookup_analyzed.find(neighborhood_cell) == cells_lookup_analyzed.end()){
                                    stack_cells.push(neighborhood_cell);
                                }
                            }
                        }


                    }
                }
            }


        }
        if(cells_ouput_analyzed.size() == input_cells.size()){
            outputs_reached.push_back(n);
        }

    }
    if(outputs_reached.size() == ouput_cells.size()){
        return true;
    }
    return false;


}





